package novosTreinadores;

import java.util.ArrayList;
import java.util.Arrays;

import org.neuroph.core.learning.SupervisedTrainingElement;
import org.neuroph.core.learning.TrainingSet;
import org.neuroph.nnet.MultiLayerPerceptron;
import org.neuroph.nnet.learning.DynamicBackPropagation;

import treinadores.CalculosAuxiliares;

import interfaces.ITreinadorRN;

public class TreinadorVoltarMinhaArea implements ITreinadorRN {

	/**
	 * Número de entradas para a rede neural
	 */
	int numeroDeEntradas = 5;
	/**
	 * Número de saídas para a rede neural São 4 saídas, que implicam qual campo
	 * de um setor o jogador vai se mover
	 */
	int numeroDeSaidas = 6;
	/**
	 * Arquivo correspondente a rede neural
	 */
	String MLP_FILE = "voltarMinhaArea.nnet";
	/**
	 * Nome do arquivo de testes
	 */
	String TS_FILE = "voltarMinhaArea.set";
	/**
	 * Conjunto de treinamento com 5 campos de entrada (vide problema) e 2
	 * campos de saida
	 * 
	 * Times de 3 jogadores e um refém cada implicam em 8 entradas
	 * 
	 */
	TrainingSet training = new TrainingSet(numeroDeEntradas, numeroDeSaidas);
	/**
	 * Classe necessária para realizar o backpropagation
	 */
	DynamicBackPropagation learning;
	/**
	 * MultilayerPerceptron ou rede neural
	 */
	MultiLayerPerceptron mlp;

	/**
	 * Cria uma rede neural para testes O conteúdo dela é salvo em um arquivo
	 */
	public TreinadorVoltarMinhaArea() {
		// necessário para realizar o backPropagation
		learning = new DynamicBackPropagation();
		learning.setMaxIterations(500);
		learning.setLearningRate(0.7);
		// Número de camadas
		ArrayList<Integer> camadas = new ArrayList<Integer>();
		// camada 1 : 2 neuronios
		camadas.add(numeroDeEntradas);
		// Camada 2 : 12 neuronios
		camadas.add(12);
		// Camada 3 : 2 neuronios
		camadas.add(numeroDeSaidas);
		// Cria um perceptron
		mlp = new MultiLayerPerceptron(camadas);
		// adiciona as entradas de treinamento
		adicionaEntradasDeTreinamento();
	}

	/**
	 * Adiciona entradas para fazer o treinamento
	 */
	public void adicionaEntradasDeTreinamento() {
		// aqui são adicionadas algumas entradas aleatórias e calculadas algumas
		// saídas
		// Algoritmo preliminar de treinamento, não sei se vai ficar
		// satisfatório
		// o bastante.
		// A RN funciona para cada jogador
		// O treinamento tenta elimiar redundâncias de posições iguais de
		// jogadores

		for (int posicaoJogadorNossoTime = 0; posicaoJogadorNossoTime <= 15; posicaoJogadorNossoTime++) {
			for (int posicaoJogadorAdversario1 = 0; posicaoJogadorAdversario1 <= 15; posicaoJogadorAdversario1++) {
				for (int posicaoJogadorAdversario2 = 1; posicaoJogadorAdversario2 <= 15; posicaoJogadorAdversario2 += 4) {
					for (int posicaoJogadorAdversario3 = 2; posicaoJogadorAdversario3 <= 15; posicaoJogadorAdversario3 += 4) {
						for (int posicaoJogadorAdversario4 = 3; posicaoJogadorAdversario4 <= 15; posicaoJogadorAdversario4 += 4) {
								int[] jogadores = { posicaoJogadorNossoTime,
										posicaoJogadorAdversario1,
										posicaoJogadorAdversario2,
										posicaoJogadorAdversario3,
										posicaoJogadorAdversario4 };

								double[] saidaDesejada = getSaidaDesejada(
												posicaoJogadorNossoTime,
												Arrays.copyOfRange(jogadores,
														1, jogadores.length-1),jogadores[jogadores.length-1]);
								training.addElement(new SupervisedTrainingElement(
								// entrada
										new double[] { posicaoJogadorNossoTime,
												posicaoJogadorAdversario1,
												posicaoJogadorAdversario2,
												posicaoJogadorAdversario3,
												posicaoJogadorAdversario4 },
										// saida
										saidaDesejada));
						}
					}
				}
			}
		}
	}
	
	//São 3 colunas de 2 bits cada indicando o caminho
	
	private double [] getSaidaDesejada(int posicaoJogadorNossoTime, int [] adversarios, int posicaoRefem){
		double [] saida = new double [6];
		
		for(int i = 0; i < saida.length; i++)
		{
			saida[i] = 0;
		}
	    
		int linhaAtual = (int)Math.abs((posicaoJogadorNossoTime/4) -3);
		int novaPosicaoJogador = posicaoJogadorNossoTime;
		
		while(linhaAtual < 3)
		{
			//Pega a coluna que o jogador deve andar para a próxima coluna
			int novaColuna = CalculosAuxiliares.acharColunaVaziaRetornar(novaPosicaoJogador,adversarios);
			//indicando a coluna para onde o jogador deve ir agora na RN
			double [] colunaSaida = CalculosAuxiliares.toBinaryDouble2(novaColuna);
			saida[linhaAtual*2] = colunaSaida[0];
			saida[linhaAtual*2+1] = colunaSaida[1];
			//incrementa a linha atual
			linhaAtual++;
			novaPosicaoJogador = (Math.abs(linhaAtual-3))*4 + novaColuna;
		}
		
		return saida;		
	}

	/**
	 * Treina a rede e salva no arquivo do conjunto de treinamento
	 */
	public void treinarRede() {
		// Treina a rede!
		mlp.learnInSameThread(training, learning);
	}

	@Override
	public void treinar() {
		// treina a rede
		treinarRede();
		// salvando arquivos
		mlp.save(MLP_FILE);
		training.save(TS_FILE);
	}
}